﻿#region COPYRIGHT© 2009-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using FlitBit.Core;
using FlitBit.Parse;

namespace FlitBit.Json
{

	public enum VisitorStrategy
	{
		DepthFirst = 0,
		BreadthFirst = 1,
	}

	public abstract class JsonValue : JsonNode, IConvertible
	{
		static readonly int CHashCodeSeed = typeof(JsonValue).AssemblyQualifiedName.GetHashCode();

		static WeakReference<JsonTokenizer> __tokenizer;

		protected JsonValue(JsonNodeKind kind)
			: base(kind)
		{}

		public void VisitTransform(Func<JsonNode, JsonNode> transform)
		{
			VisitTransform(transform, VisitorStrategy.DepthFirst);
		}

		public void VisitTransform(Func<JsonNode, JsonNode> transform, VisitorStrategy strategy)
		{
			if (strategy == VisitorStrategy.DepthFirst)
			{
				PerformDepthFirstTransformFilter(transform);
			}
			else
			{
				PerformBreadthFirstTransformFilter(transform);
			}
		}

		protected internal virtual void PerformBreadthFirstTransformFilter(Func<JsonNode, JsonNode> filter)
		{}

		protected internal virtual void PerformDepthFirstTransformFilter(Func<JsonNode, JsonNode> filter)
		{}

		/// <summary>
		/// Determines if the instance is equal to another.
		/// </summary>
		/// <param name="obj">the other</param>
		/// <returns><em>true</em> if equal; otherwise <em>false</em></returns>
		public override bool Equals(object obj)
		{
			return (obj is JsonValue) && Equals((JsonValue)obj);
		}

		/// <summary>
		/// Calculates the instance's hashcode.
		/// </summary>
		/// <returns>
		/// The instance's hashcode.
		/// </returns>
		public override int GetHashCode()
		{
			const int prime = 999067;
			var code = CHashCodeSeed * prime;
			code ^= base.GetHashCode() * prime;
			return code;
		}

		/// <summary>
		/// Determines if the instance is equal to another.
		/// </summary>
		/// <param name="other">the other</param>
		/// <returns><em>true</em> if equal; otherwise <em>false</em></returns>
		public bool Equals(JsonValue other)
		{
			return Equals((JsonNode) other);
		}

		public static bool operator ==(JsonValue lhs, JsonValue rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			return lhs.Equals(rhs);
		}

		public static bool operator ==(JsonValue lhs, string rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			return String.Equals(lhs.ToString(CultureInfo.InvariantCulture), rhs);
		}

		public static bool operator ==(string lhs, JsonValue rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs == null);
			}
			if ((object) rhs == null)
			{
				return false;
			}
			return String.Equals(lhs, rhs.ToString(null));
		}

		public static bool operator ==(JsonValue lhs, decimal rhs)
		{
			if ((object) lhs == null)
			{
				return false;
			}
			return lhs.ToDecimal(null) == rhs;
		}

		public static bool operator ==(decimal lhs, JsonValue rhs)
		{
			if ((object) rhs == null)
			{
				return false;
			}
			return lhs == rhs.ToDecimal(null);
		}

		public static bool operator ==(JsonValue lhs, int rhs)
		{
			if ((object) lhs == null)
			{
				return false;
			}
			return lhs.ToInt32(null) == rhs;
		}

		public static bool operator ==(int lhs, JsonValue rhs)
		{
			if ((object) rhs == null)
			{
				return false;
			}
			return lhs == rhs.ToInt32(null);
		}

		public static implicit operator String(JsonValue literal)
		{
			if ((object) literal == null)
			{
				return null;
			}
			return literal.ToString(null);
		}

		public static implicit operator decimal(JsonValue literal)
		{
			if ((object) literal == null)
			{
				return default(decimal);
			}
			return literal.ToDecimal(null);
		}

		public static implicit operator int(JsonValue literal)
		{
			if ((object) literal == null)
			{
				return default(int);
			}
			return literal.ToInt32(null);
		}

		public static implicit operator bool(JsonValue literal)
		{
			if ((object) literal == null)
			{
				return default(bool);
			}
			return literal.ToBoolean(null);
		}

		public static implicit operator JsonValue(string value)
		{
			return new JsonStringLiteral(value);
		}

		public static implicit operator JsonValue(decimal value)
		{
			return new JsonNumericLiteral(value);
		}

		public static implicit operator JsonValue(int value)
		{
			return new JsonNumericLiteral(value);
		}

		public static implicit operator JsonValue(long value)
		{
			return new JsonNumericLiteral(value);
		}

		public static bool operator !=(JsonValue lhs, JsonValue rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			return !lhs.Equals(rhs);
		}

		public static bool operator !=(JsonValue lhs, string rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			return !String.Equals(lhs.ToString(null), rhs);
		}

		public static bool operator !=(string lhs, JsonValue rhs)
		{
			if ((object) lhs == null)
			{
				return ((object) rhs != null);
			}
			if ((object) rhs == null)
			{
				return false;
			}
			return !String.Equals(lhs, rhs.ToString(null));
		}

		public static bool operator !=(JsonValue lhs, decimal rhs)
		{
			if ((object) lhs == null)
			{
				return true;
			}
			return lhs.ToDecimal(null) != rhs;
		}

		public static bool operator !=(decimal lhs, JsonValue rhs)
		{
			if ((object) rhs == null)
			{
				return true;
			}
			return lhs != rhs.ToDecimal(null);
		}

		public static bool operator !=(JsonValue lhs, int rhs)
		{
			if ((object) lhs == null)
			{
				return true;
			}
			return lhs.ToInt32(null) != rhs;
		}

		public static bool operator !=(int lhs, JsonValue rhs)
		{
			if ((object) rhs == null)
			{
				return true;
			}
			return lhs != rhs.ToInt32(null);
		}

		#region IConvertible Members

		public abstract TypeCode GetTypeCode();

		public virtual bool ToBoolean(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual byte ToByte(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual char ToChar(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual DateTime ToDateTime(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual decimal ToDecimal(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual double ToDouble(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual short ToInt16(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual int ToInt32(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual long ToInt64(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual sbyte ToSByte(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual float ToSingle(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual string ToString(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual object ToType(Type conversionType, IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual ushort ToUInt16(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual uint ToUInt32(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		public virtual ulong ToUInt64(IFormatProvider provider)
		{
			throw new InvalidCastException();
		}

		#endregion

		static JsonTokenizer Tokenizer
		{
			get
			{
				JsonTokenizer result;
				if (__tokenizer == null || !__tokenizer.TryGetStrongTarget(out result))
				{
					__tokenizer = new WeakReference<JsonTokenizer>(result = new JsonTokenizer(true));
				}
				return result;
			}
		}

		public static JsonValue Parse(string json)
		{
			Contract.Requires<ArgumentNullException>(json != null);
			Contract.Requires<ArgumentException>(json.Length > 0);
			Contract.Ensures(Contract.Result<JsonValue>() != ((JsonValue)null));
			try
			{
				var raw = new TokenizerState<JsonTokenKind>(Tokenizer.Tokenize(json));
				return ExpectValue(raw);
			}
			catch (ParseException pex)
			{
				throw new FormatException(pex.Message);
			}
		}

		public static bool TryParse(string json, out JsonValue value)
		{
			try
			{
				var raw = Tokenizer.Tokenize(json).ToArray();
				if (!raw.Any(t => t.IsError))
				{
					value = ExpectValue(new TokenizerState<JsonTokenKind>(raw));
					return true;
				}
			}
			catch (ParseException)
			{
				/* fall through, return false */
			}
			value = default(JsonValue);
			return false;
		}

		protected internal static JsonValue ExpectValue(TokenizerState<JsonTokenKind> tokens)
		{
			var tk = tokens.ExpectAtAny(
																 JsonTokenKind.Left_Brace_Symbol,
																JsonTokenKind.Left_Curly_Brace_Symbol,
																JsonTokenKind.StringLiteral,
																JsonTokenKind.NumericLiteral,
																JsonTokenKind.True,
																JsonTokenKind.False,
																JsonTokenKind.Null);
			switch (tk.Kind)
			{
				case JsonTokenKind.Left_Brace_Symbol:
					return JsonArray.ExpectArray(tokens);
				case JsonTokenKind.Left_Curly_Brace_Symbol:
					return JsonObject.ExpectObject(tokens);
				case JsonTokenKind.StringLiteral:
					return JsonStringLiteral.ExpectStringLiteral(tokens);
				case JsonTokenKind.NumericLiteral:
					return JsonNumericLiteral.ExpectNumericLiteral(tokens);
				case JsonTokenKind.Null:
					tokens.MoveNext();
					return JsonNull.Object;
				case JsonTokenKind.True:
					tokens.MoveNext();
					return JsonBoolean.True;
				case JsonTokenKind.False:
					tokens.MoveNext();
					return JsonBoolean.False;
				case JsonTokenKind.Identifier:
				case JsonTokenKind.Right_Brace_Symbol:
				case JsonTokenKind.Right_Curly_Brace_Symbol:
				case JsonTokenKind.Plus_Symbol:
				case JsonTokenKind.Minus_Symbol:
				case JsonTokenKind.Whitespace:
				case JsonTokenKind.Comment:
					throw new ParseException(String.Concat("Unexpected token: ", tk), tk.Capture.Position);
			}
			throw new ParseException(String.Concat("Unexpected token: ", tk), tk.Capture.Position);
		}
	}
}